<template>
  <div class="app-container">
    <div class="robot">
      <el-row type="flex" justify="space-between" align="middle">
        <el-form
          :model="queryParams"
          ref="queryForm"
          size="small"
          :inline="true"
          label-width="90px">
          <el-form-item label="所属项目" prop="status">
            <el-select
              ref="selectRef1"
              @blur="handleBlur($refs.selectRef1)"
              v-model="queryParams.channelId"
              placeholder="请选择项目"
              @change="changeProject">
              <el-option
                v-for="item in channelList"
                :key="item.value"
                :label="item.label"
                :value="item.value" />
            </el-select>
          </el-form-item>

          <el-form-item label="选择机器人">
            <el-select
              v-model="selectedRobot"
              placeholder="请选择"
              filterable
              @change="changeRobot">
              <el-option
                v-for="item in botOptions"
                :key="item.value"
                :label="item.label"
                :value="item.value">
              </el-option>
            </el-select>
          </el-form-item>

          <el-form-item label="保存日志" v-if="false">
            <el-select v-model="saveLog" placeholder="请选择">
              <el-option
                v-for="item in saveLogOpt"
                :key="item.key"
                :label="item.label"
                :value="item.value">
              </el-option>
            </el-select>
          </el-form-item>
        </el-form>
        <el-button type="primary" icon="el-icon-delete" @click="clear">清空</el-button>
      </el-row>
      <div class="robot-content" ref="robotContent">
        <div v-if="curChatList && curChatList.length > 0">
          <div class="chat-item" v-for="(item, index) in curChatList" :key="index">
            <!-- <div class="chat-item-time">{{ parseTime(item.createTime) }}</div> -->
            <div class="chat-item-text" :class="{ 'flex-end': item.from === 'user' }">
              <div class="pd" :class="item.from === 'user' ? 'from-user' : 'from-robot'">
                <div
                  style="width: 200px; height: 60px"
                  v-if="item.text.length === 0 && item.from === 'robot'"
                  class="pd from-robot"
                  v-loading="isAnswering"
                  element-loading-text="正在思考中"
                  element-loading-spinner="el-icon-loading"></div>
                <div>{{ item.text }}</div>
                <div class="chat-item-text-bottom">
                  <el-button v-if="item.jsonData" type="text" @click.stop="checkJson(item.jsonData)"
                    >查看json</el-button
                  >
                  <el-button
                    v-if="item.vectorList && item.vectorList.length > 0"
                    type="text"
                    @click.stop="checkVector(item.vectorList)"
                    >查看向量查询结果</el-button
                  >
                </div>
              </div>
            </div>
          </div>
        </div>
        <!-- <div
          v-if="isAnswering"
          class="pd from-robot"
          v-loading="isAnswering"
          element-loading-text="努力回答中"
          element-loading-spinner="el-icon-loading"></div> -->
        <el-empty v-if="curChatList.length === 0 && !isAnswering" description="暂无对话"></el-empty>
      </div>
      <div class="robot-tool">
        <el-input
          type="textarea"
          :rows="3"
          placeholder="请输入您的问题"
          resize="none"
          v-model="question"
          :disabled="isAnswering"
          @keydown.enter.native="keydownEnterInput">
        </el-input>
        <el-button
          type="primary"
          class="send-buttton"
          :disabled="isAnswering"
          :loading="isAnswering"
          @click.stop="send">
          {{ isAnswering ? '努力回答中' : '发送' }}</el-button
        >
      </div>
    </div>
    <el-dialog title="查看JSON" :visible.sync="showDialog">
      <vue-json-viewer :value="curJsonData" copyable @copied="copyJson">
        <template slot="copy"><i class="el-icon-document-copy">复制</i> </template>
      </vue-json-viewer>
      <span slot="footer" class="dialog-footer">
        <el-button
          type="primary"
          v-clipboard:copy="JSON.stringify(curJsonData)"
          v-clipboard:success="copyJson"
          >复 制</el-button
        >
        <el-button @click="showDialog = false">关 闭</el-button>
      </span>
    </el-dialog>
    <el-dialog title="向量查询结果" :visible.sync="showVecDialog">
      <div class="vector-item" v-for="(vec, index) in curVectorList" :key="index">
        <span>{{ index + 1 }}.</span>
        <div v-if="vec.question">参考问题：{{ vec.question }}</div>
        <el-collapse v-if="vec.answer">
          <el-collapse-item>
            <template slot="title">查看参考答案</template>
            <div v-html="vec.answer"></div>
          </el-collapse-item>
        </el-collapse>
        <div v-if="vec.content">{{ vec.content }}</div>
        <div style="color: #67c23a">答案得分：{{ vec.score }}</div>
        <div v-if="vec.filename">文件名：{{ vec.filename }}</div>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button @click="showVecDialog = false">关 闭</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
import vueJsonViewer from 'vue-json-viewer'
import { listSubChannel } from '@/api/chat/subChannel'
import { v4 as uuidv4 } from 'uuid'
import { robotChat } from '@/api/chat/debugRobot'
import { listChannel } from '@/api/chat/channel'

export default {
  name: 'DebugRobot',
  components: {
    vueJsonViewer
  },
  data() {
    return {
      allList: [],
      curVectorList: [],
      showVecDialog: false,
      showDialog: false,
      isAnswering: false,
      curJsonData: {},
      selectedRobot: '',
      question: '',
      botOptions: [],
      saveLogOpt: [
        { key: 1, value: true, label: '是' },
        { key: 2, value: false, label: '否' }
      ],
      saveLog: false,
      curChatList: [],
      robotChatRecord: [],
      form: {},
      channelList: [],
      queryParams: {
        channelId: ''
      }
    }
  },
  created() {
    this.getlistChannel()
  },
  async mounted() {
    await this.queryRobot()
    // this.selectedRobot = this.botOptions[0].value;
    // this.initRobotChatRecord();
    this.changeRobot(this.selectedRobot)
  },
  methods: {
    async robotChatStream() {
      this.isAnswering = true
      const obj = this.botOptions.find(item => item.value === this.selectedRobot) || {}
      // console.log(obj, 'obj')
      const { appKey, appSecret, conversationId } = obj
      const params = {
        appKey,
        appSecret,
        question: this.question,
        conversationId,
        answerChannel: ['3'],
        stream: true,
        debug: true,
        saveLog: this.saveLog,
        saveCache: false
      }
      let robotMsg = {
        from: 'robot',
        text: '',
        dialogId: uuidv4(),
        vectorList: [],
        jsonData: {}
      }
      this.curChatList.push(robotMsg)

      // 需要用fetch API
      // 第一次await 等待的是服务器响应头到达客户端
      const response = await fetch('https://chattax.yunhelp.com:883/server/rest/chat', {
        method: 'POST',
        headers: {
          // Accept: 'text/event-stream',
          'Content-Type': 'application/json',
          isToken: false
        },
        body: JSON.stringify(params)
      })
      // console.log(response, 'response')
      console.log('这里会执行多少次？')
      this.question = ''
      // 到这里只拿到响应头，拿不到响应体
      // 第二次await 等待的是服务器响应体到达客户端
      // 这一步是等待所有的响应体这一整块全部到达客户端那么这个等待才结束。响应头等待是很快的，响应体一般数据量大就慢了，耗时长，所以卡在响应体.
      // const data = await response.text()
      // console.log(data, 'data')
      // 所以现在不用等全部传输完成，而是一块一块读，你给我多少我读多少。比作服务端的给客户端的数据现象成水流，一点一点的流向客户端，流多少读多少。故称作流式读取。
      // response.body 的类型是 ReadableStream（可读流），可以一块一块读出来。它里面提供一个 getReader() 方法，用来获取一个可读流的读取器。通过这个读取器就可以一块一块读了
      const reader = response.body.getReader()
      // 怎么读？reader.read()  读多少？读一块。那一块又是多少呢？这个要看操作系统的缓冲区，复杂，深度解释要解释到TCP，不展开说了。读一块也是需要时间的，所以要等待await。
      // 这个读一小块是很快的，所以等待时间不会很长。读完之后拿到一个对象
      // const { done, value } = await reader.read()
      // console.log(done, value, 'done, value') // done: boolean 有没有读完  value: Uint8Array(3) [ 123, 34, 99 ]  数据，一个字节数组，这是一个es6的类型化数组
      // 但是我要的数据不是字节数组，是一个字符。
      // 所以需要一个文字解码器，由于字节数组保存的是文字编码。所以把字节数组里边的东西进行解码。
      // 创建一个解码器
      const decoder = new TextDecoder('utf-8')
      // const { done, value } = await reader.read()
      // 把字节数组通过这个解码器 解码 成文本
      // const txt = decoder.decode(value)
      // console.log(done, txt)  // txt 变成可识别的数据（文本）
      // 到这里只读取、解码了第一块数据，那剩下的数据怎么读？接着重复这个动作呗。哪个动作重复？读一块数据，解码一块数据。
      // 所以可以给个循环，每一次循环都读取一块数据，直到读取完成。 什么时候读取完不读了？done为true
      // while (1) {
      //   const { done, value } = await reader.read()
      //   if (done) {
      //     break
      //   }
      //   const txt = decoder.decode(value)
      //   // 打印添加背景颜色
      //   console.log(`%c ${txt}`, 'background: green')
      // }
      // 接下来就是，一块一块数据读完之后，如何把数据展示出来呢？
      // 根据接口字段，不是直接拿到文本答案，是一个数据体
      while (1) {
        const { done, value } = await reader.read()
        if (done) {
          break
        }
        const chunk = decoder.decode(value)
        // console.log(chunk, typeof chunk, 'chunk')
        // 数据处理
        const result = chunk
          // 1. 按换行符分割为数组
          .split('\n')
          // 2. 清理每行数据
          .map(line => {
            // 移除可能的SSE前缀和首尾空格
            const cleaned = line.replace(/^data:\s*/, '').trim()
            // 验证JSON格式（可选）
            try {
              JSON.parse(cleaned)
              return cleaned
            } catch (e) {
              console.warn('无效JSON数据:', cleaned)
              return null
            }
          })
          // 3. 过滤无效条目
          .filter(line => line && line.length > 0)
        console.log(result, 'result')
        const parsedData = result
        for (const line of parsedData) {
          const { answer, yxPyDocQuery } = JSON.parse(line)
          // 渲染到页面
          robotMsg.text = answer
          // 出现yxPyDocQuery代表生成答案完成
          if (yxPyDocQuery) {
            // 关闭loading
            this.isAnswering = false
            // console.log(yxPyDocQuery, 'yxPyDocQuery')
            robotMsg.jsonData = JSON.parse(line)
            // console.log(this.isAnswering, 'isAnswering'
            let { qas = [], paragraphs = [] } = yxPyDocQuery
            robotMsg.vectorList = qas.concat(paragraphs)
          }
          if (!line.answer && line.answer?.length === 0) {
            // 第一次时，直接添加
            // console.log(robotMsg, 'robotMsg---第一次时')
            // this.curChatList.push(robotMsg)
          } else {
            this.curChatList[this.curChatList.length - 1].text = robotMsg.text
          }
          let target = this.robotChatRecord.find(item => item.value === this.selectedRobot) || {}
          target.chatList = this.curChatList
          console.log(robotMsg, 'robotMsg')
          console.log(this.curChatList, 'this.curChatList')
          this.scrollToBottom()
        }
      }
    },
    changeProject() {
      this.$forceUpdate()
      this.queryRobot()
      // this.initRobotChatRecord();
    },
    checkVector(data) {
      this.curVectorList = data
      this.showVecDialog = true
    },
    keydownEnterInput(e) {
      if (e.ctrlKey) {
        this.question = this.question += '\n'
        return
      }
      e.cancelBubble = true
      e.stopPropagation()
      e.preventDefault()
      this.send()
    },
    scrollToBottom() {
      setTimeout(() => {
        if (this.$refs.robotContent.scrollHeight > this.$refs.robotContent.clientHeight) {
          this.$refs.robotContent.scrollTop = this.$refs.robotContent.clientHeight
        }
      }, 500)
    },
    copyJson() {
      this.$message.success('复制成功')
    },
    clear() {
      // 对应机器人清空chatlist和更新uuid
      this.curChatList = []
      const uuid = uuidv4()
      const obj = this.robotChatRecord.find(item => item.value === this.selectedRobot) || {}
      obj.chatList = []
      obj.conversationId = uuid
      this.botOptions.find(item => item.value === this.selectedRobot).conversationId = uuid
    },
    initRobotChatRecord() {
      console.log(this.botOptions, '初始化')
      if (this.botOptions && this.botOptions.length > 0) {
        this.robotChatRecord = this.botOptions.map(item => {
          item.conversationId = uuidv4()
          console.log(item)
          return {
            ...item,
            chatList: []
          }
        })
      }
    },
    changeRobot(val) {
      console.log(val, 'change')
      const target = this.robotChatRecord.find(item => item.value === val) || {}
      this.curChatList = target.chatList || []
      this.isAnswering = false
    },
    async robotChat() {
      this.isAnswering = true
      const obj = this.botOptions.find(item => item.value === this.selectedRobot) || {}
      console.log(obj, 'obj')
      const { appKey, appSecret, conversationId } = obj
      const params = {
        appKey,
        appSecret,
        question: this.question,
        conversationId,
        answerChannel: ['3'],
        stream: true,
        debug: true,
        saveLog: this.saveLog,
        saveCache: false
      }
      const res = await robotChat(params)
      this.isAnswering = false
      // axios方法只会在所有请求list完成才返回结果
      console.log(res, 'res')
      // res是一个字符串,去除'data:'
      // const strJson = res.substr(5);
      const toParseData = res[0]
      const { answer, yxPyDocQuery } = toParseData
      if (yxPyDocQuery) {
        let { qas, paragraphs } = yxPyDocQuery
        console.log(qas)
        if (!qas) qas = []
        if (!paragraphs) paragraphs = []
        const concatList = qas.concat(paragraphs)
        // 组装接收的消息
        let robotMsg = {
          from: 'robot',
          text: answer,
          dialogId: uuidv4(),
          vectorList: concatList,
          jsonData: toParseData
        }
        this.curChatList.push(robotMsg)
        let target = this.robotChatRecord.find(item => item.value === this.selectedRobot) || {}
        target.chatList = this.curChatList
        this.question = ''
        this.scrollToBottom()
      } else {
        // 组装接收的消息
        let robotMsg = {
          from: 'robot',
          text: answer,
          dialogId: uuidv4(),
          vectorList: [],
          jsonData: toParseData
        }
        this.curChatList.push(robotMsg)
        let target = this.robotChatRecord.find(item => item.value === this.selectedRobot) || {}
        target.chatList = this.curChatList
        this.question = ''
        this.scrollToBottom()
      }
    },
    async queryRobot() {
      if (this.queryParams.channelId == null || this.queryParams.channelId == '') {
        return
      }
      const res = await listSubChannel({
        channelId: this.queryParams.channelId,
        pageNum: 1,
        pageSize: 500
      })
      const { code, msg, rows } = res
      if (code !== 200) return this.$message.error(msg)
      this.botOptions = rows.map(item => {
        return {
          value: item.id,
          label: item.botName,
          appKey: item.appKey,
          appSecret: item.appSecret
        }
      })
      console.log(this.botOptions, '返回')
      this.selectedRobot = this.botOptions.length > 0 ? this.botOptions[0].value : ''
      if (this.botOptions.length > 0) {
        this.changeRobot(this.botOptions[0].value)
      }
      this.initRobotChatRecord()
    },
    checkJson(data) {
      this.curJsonData = data
      this.showDialog = true
    },
    send() {
      if (this.selectedRobot == '') return this.$message.warning('请选择机器人！')
      if (!this.question) return this.$message.warning('请输入问题！')
      // 组装发送的消息 curChatList赋值给robotChatRecord对应的chatList
      let userMsg = { from: 'user', text: this.question, dialogId: uuidv4() }
      this.curChatList.push(userMsg)
      let target = this.robotChatRecord.find(item => item.value === this.selectedRobot) || {}
      target.chatList = this.curChatList
      this.scrollToBottom()
      // this.robotChat();
      this.robotChatStream()
    },

    //查询项目列表
    getlistChannel(deptId) {
      listChannel({
        pageNum: 1,
        pageSize: 10000,
        deptId
      }).then(res => {
        this.channelList = res.rows.map(item => ({
          value: item.id,
          label: item.title,
          deptId: item.deptId
        }))
        this.allList = res.rows

        if (this.channelList && this.channelList.length > 0) {
          this.queryParams.channelId = this.channelList[0].value
          this.queryRobot()
          // this.initRobotChatRecord();
          // this.getList();
        }
      })
    }
  }
}
</script>
<style lang="scss" scoped>
.app-container {
  padding-right: 0;
}
.robot {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  height: calc(100vh - 130px);
}
.robot-content {
  flex: 1;
  overflow: auto;
  padding-top: 10px;
  padding-right: 20px;
  // background-color: #f5f5f5;
  .chat-item {
    width: 100%;
    margin-bottom: 10px;
    position: relative;
    &-time {
      font-size: 13px;
      color: #999;
      display: flex;
      justify-content: center;
    }
    &-text {
      .pd {
        padding: 10px 15px;
      }
      &-bottom {
        display: flex;
        justify-content: space-between;
        align-items: center;
      }
    }
    .flex-end {
      display: flex;
      justify-content: flex-end;
    }
    .from-user {
      display: flex;
      justify-content: flex-end;
      width: fit-content;
      max-width: 80%;
      background-color: var(--current-color);
      color: #fff;
      border-radius: 15px 0 10px 0;
    }
    .from-robot {
      width: fit-content;
      max-width: 80%;
      border: 1px solid #ccc;
      border-radius: 0 15px 0 10px;
    }
  }
}
.robot-tool {
  position: relative;
  margin-top: 20px;
  margin-right: 20px;
  .send-buttton {
    position: absolute;
    bottom: 10px;
    right: 20px;
  }
}

.vector-item {
  margin-top: 10px;
  padding-bottom: 13px;
  border-bottom: 2px dashed #eee;
  div {
    margin-top: 3px;
  }
}
::v-deep .el-dialog {
  .el-dialog__body {
    max-height: 600px;
    overflow: auto;
    &:hover {
      .el-icon-document-copy {
        opacity: 1;
      }
    }
  }
  .el-icon-document-copy {
    position: fixed;
    margin-left: -22px;
    background-color: #fff;
    padding: 5px;
    border-radius: 2px;
    opacity: 0;
    &::before {
      margin-right: 5px;
    }
  }
}
.el-row {
  box-shadow: 0 3px 5px #eee;
  margin-right: 20px;
}
::v-deep .el-collapse {
  border: none;
  .el-collapse-item__header {
    height: 30px;
    line-height: 30px;
    color: var(--current-color);
    border: none;
  }
}
</style>
